/**
 * 
 */
package org.bean.spiral.processor;


import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.beanutils.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.bean.spiral.annotations.Key;
import org.bean.spiral.annotations.Root;


/**
 * @author saiyez
 * @param <T>
 * 
 */
@SuppressWarnings("unchecked")
public class RootBuilder<T> {

  List<T> maintainedObjects = new ArrayList<T>();

  /**
   * 
   */
  public <E> T buildInstrance(E element) {

    T result = null;
    try {
      result = createRoot(element);
      if (maintainedObjects.contains(result)) {
        result = maintainedObjects.get(maintainedObjects.indexOf(result));
      } else {
        System.err.println("Adding New");
        maintainedObjects.add(result);
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    return result;
  }

  /**
   * 
   * @param <E>
   * @param <T>
   * @param element
   * @return
   * @throws IllegalAccessException
   * @throws InstantiationException
   * @throws IntrospectionException
   * @throws InvocationTargetException
   * @throws IllegalArgumentException
   * @throws NoSuchMethodException
   */
  private <E> T createRoot(E element) throws InstantiationException,
      IllegalAccessException, IntrospectionException, IllegalArgumentException,
      InvocationTargetException, NoSuchMethodException {

    T newClass = null;
    Root root = element.getClass().getAnnotation(Root.class);
    Class<T> requiredClass = (Class<T>) root.rootClass();
    newClass = requiredClass.newInstance();

    List<Field> keys = getKeys(element.getClass());
    PropertyDescriptor descriptor = null;

    for (Field field : keys) {
      Key rootField = field.getAnnotation(Key.class);
      String rootKey = rootField.rootField();
      if (StringUtils.equals(rootKey, Key.AS_FIELD_NAME)) {
        rootKey = field.getName();
      }
      descriptor = PropertyUtils.getPropertyDescriptor(newClass, rootKey);
      descriptor.getWriteMethod().invoke(newClass,
                                         getFieldValue(field, element));

    }

    return newClass;
  }

  /**
   * 
   * @param rootClass
   * @return
   */
  private List<Field> getKeys(Class<?> rootClass) {

    List<Field> keys = new ArrayList<Field>();
    for (Field field : rootClass.getDeclaredFields()) {
      if (field.getAnnotation(Key.class) != null) {
        keys.add(field);
      }
    }
    return keys;
  }

  /**
   * 
   * @param <E>
   * @param field
   * @param instance
   * @return
   */
  private <E> Object getFieldValue(final Field field, final E instance) {

    return AccessController.doPrivileged(new PrivilegedAction() {

      public Object run() {

        try {
          field.setAccessible(true);
          return field.get(instance);
        } catch (IllegalAccessException e) {
          e.printStackTrace();
        }
        return null;
      }
    });
  }

}
